package ro.pub.cs.stadium.output.verilog;

import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Map;

import ro.pub.cs.stadium.document.IDocument;
import ro.pub.cs.stadium.document.IState;
import ro.pub.cs.stadium.document.ITransition;
import ro.pub.cs.stadium.document.logic.Document;
import ro.pub.cs.stadium.document.logic.State;
import ro.pub.cs.stadium.document.logic.Transition;
import ro.pub.cs.stadium.document.logic.Variable;
import ro.pub.cs.stadium.document.logic.VariableList;
import ro.pub.cs.stadium.plugin.IOutputPlugin;
import ro.pub.cs.stadium.plugin.PluginSettings;

public class VerilogOutputPlugin implements IOutputPlugin {
	
	private String NL = System.getProperty("line.separator");
	
	public Object getPlugin(int implementation) {
		// TODO Auto-generated method stub
		return null;
	}

	public int getPluginImplementations() {
		// TODO Auto-generated method stub
		return 0;
	}

	public void initialise(PluginSettings settings) {
		// TODO Auto-generated method stub

	}

	public void writeDocument(IDocument document, OutputStream stream) {
		OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("US-ASCII"));
		Document doc;
		
		if (document instanceof Document)
			doc = (Document)document;
		else {
			return;
		}
		
		try {
			writeHeader(doc, writer);
			writeVariablesDeclaration(doc, writer);
			writeAdvanceLogic(doc, writer);
			writeOutputLogic(doc, writer);
			writeTransitionLogic(doc, writer);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void writeHeader(Document doc, OutputStreamWriter out)
			throws IOException {
		StringBuffer varList = new StringBuffer();
		out.write("`timescale 1s/1s" + NL);
		
		out.write("module verilog_module(");
		for (Variable var : doc.getVariableList().getVariables()) {
			varList.append(var.getName());
			varList.append(", ");
		}
		
		varList.append("CLK");
		varList.append(");");
		out.write(varList.toString());
		out.write(NL);
	}
	
	private void writeVariablesDeclaration(Document doc, OutputStreamWriter writer)
			throws IOException {
		
		State state;
		ArrayList<Variable> vars = new ArrayList<Variable>();
		
		//declaration of inputs
		writer.write("//input ports" + NL);
		writer.write("input CLK, RESET;" + NL);
		for (Variable var : doc.getVariableList().getVariables()) {
			writer.write("input " + var.getName() + ";");
			writer.write(NL);
		}
		
		//building output variables list
		for (IState s : doc.getStates().values()) {
			state = (State)s;
			if (state != null) {
				for(Variable var : state.getVariables().getVariables()) {
					if (!vars.contains(var))
						vars.add(var);
				}
			}
		}
		
		//output variables;
		writer.write("//output ports" + NL);
		for(Variable var : vars) {
			writer.write("output " + var.getName() + ";" + NL);
		}
		
		//registers for output variables;
		writer.write("//Registers for the output variables" + NL);
		for (Variable var : vars) {
			writer.write("reg " + var.getName() + ", next_" + var.getName() + ";" + NL);
		}
		
		//registers for states, 2/state
		writer.write("//state registers" + NL);
		for (IState s : doc.getStates().values()){
			state = (State)s;
			if (state != null){
				writer.write("reg " + state.getName() + ", next_" + state.getName() + ";");
				writer.write(NL);
			}
		}
	}
	
	private void writeAdvanceLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		boolean first = true;
		State s;
		Map<Variable, Boolean> initVars;
		
		out.write("always @(posedge CLK or posedge RESET)" + NL);
		out.write("begin" + NL);
		out.write("if (RESET) begin" + NL);
		for(IState state:doc.getStates().values()) {
			s = (State)state;
			if (s != null) {
				if (first) {
					first = false;
					out.write(s.getName() + " = 1;" + NL);
				}
				else {
					out.write(s.getName() + " = 0;" + NL);
				}
			}
		}
		//output variables reset
		out.write("//Output variables reset" + NL);
		if (!doc.getStates().values().isEmpty()) {
			s = (State)doc.getStates().values().iterator().next();
			initVars = s.getValues();
			for(Variable var : initVars.keySet()) {
				out.write(var.getName() + " = ");
				out.write(initVars.get(var).booleanValue() == true ? "1;" : "0;");
				out.write(NL);
			}
		}
		
		out.write("end else" + NL);
		out.write("begin" + NL);
		//states advance
		for(IState state:doc.getStates().values()) {
			s = (State)state;
			if (s != null) {
				out.write(state.getName()+ " = next_" + state.getName() + ";" + NL);
			}
		}
		
		out.write("end" + NL); //else
		out.write("end" + NL); //always
	}
	
	private void writeOutputLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		//TODO: output variables logic
		State state;
		out.write("//Output variables logic" + NL);
		//foreach state:
		//if state => output = state_value
		for (IState s : doc.getStates().values()) {
			state = (State) s;
			if (state != null) {
				
			}
		}
	}
	
	private void writeTransitionLogic(Document doc, OutputStreamWriter out)
			throws IOException {
		Transition transition;
		
		//TODO: always @(inputs or outputs or states)
		out.write(NL);
		
		//foreach transition
		//if transition_expression & initial_state => next_final_state = 1;
		for (ITransition t : doc.getTransitions().values()) {
			transition = (Transition) t;
			
			if (transition != null) {
				
			}
		}
	}
}
